Aller au contenu principal

Deep Linking : Lancer l'application depuis l'extérieur

Introduction

Le deep linking permet d'ouvrir l'application directement sur un écran spécifique via :

  • Un lien externe (URL partagée)
  • Une notification push
  • Un email ou SMS
  • Un raccourci système
  • Une autre application

Cas d'usage typiques :

  • Notification → Détails du produit
  • Email → Page de réinitialisation de mot de passe
  • Lien partagé → Profil utilisateur
  • QR Code → Écran de paiement

Configuration du Deep Linking

Android (AndroidManifest.xml)

Ajouter des intent-filter dans android/app/src/main/AndroidManifest.xml :

<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:exported="true">

<!-- Deep link HTTPS (Universal Links) -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="myapp.com"
android:pathPrefix="/product" />
</intent-filter>

<!-- Deep link avec schéma custom -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
</activity>

Explications :

  • android:scheme="https" : Liens web standards (https://myapp.com/product/123)
  • android:scheme="myapp" : Schéma custom (myapp://product/123)
  • android:host : Domaine de votre site
  • android:pathPrefix : Chemin spécifique (optionnel)

iOS (Info.plist)

Ajouter dans ios/Runner/Info.plist :

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.myapp.deeplink</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>

Pour les Universal Links (HTTPS) sur iOS : Ajouter également :

<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:myapp.com</string>
</array>

Parsing de l'URL avec onGenerateRoute

import 'package:flutter/material.dart';

class MyApp extends StatelessWidget {
const MyApp({super.key});


Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
onGenerateRoute: (settings) {
// Parser l'URL du deep link
final uri = Uri.parse(settings.name ?? '/');

// Deep link custom : myapp://product/123
if (uri.scheme == 'myapp') {
if (uri.host == 'product' && uri.pathSegments.isNotEmpty) {
final productId = int.tryParse(uri.pathSegments[0]);
if (productId != null) {
return MaterialPageRoute(
builder: (_) => ProductDetailScreen(productId: productId),
);
}
}
}

// Deep link HTTPS : https://myapp.com/product/123
if (uri.scheme == 'https' && uri.host == 'myapp.com') {
if (uri.path.startsWith('/product') && uri.pathSegments.length > 1) {
final productId = int.tryParse(uri.pathSegments[1]);
if (productId != null) {
return MaterialPageRoute(
builder: (_) => ProductDetailScreen(productId: productId),
);
}
}
}

// Route par défaut
return MaterialPageRoute(builder: (_) => const HomeScreen());
},
);
}
}

class ProductDetailScreen extends StatelessWidget {
final int productId;

const ProductDetailScreen({super.key, required this.productId});


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Produit')),
body: Center(
child: Text('Produit ID: $productId'),
),
);
}
}

class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Accueil')),
body: const Center(child: Text('Bienvenue')),
);
}
}

Gestion des paramètres de query

Les deep links peuvent contenir des paramètres de query (ex: ?query=flutter&category=tutorial).

Exemple : myapp://search?query=flutter&category=tutorial

class MyApp extends StatelessWidget {
const MyApp({super.key});


Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
onGenerateRoute: (settings) {
final uri = Uri.parse(settings.name ?? '/');

// Deep link avec query parameters
if (uri.host == 'search') {
final query = uri.queryParameters['query'];
final category = uri.queryParameters['category'];
return MaterialPageRoute(
builder: (_) => SearchScreen(
query: query ?? '',
category: category,
),
);
}

return MaterialPageRoute(builder: (_) => const HomeScreen());
},
);
}
}

class SearchScreen extends StatelessWidget {
final String query;
final String? category;

const SearchScreen({
super.key,
required this.query,
this.category,
});


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Recherche')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Recherche: $query'),
if (category != null)
Text('Catégorie: $category'),
],
),
),
);
}
}

Exemples d'intégration

Utiliser Firebase Cloud Messaging (FCM) pour envoyer des notifications avec deep links.

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';

class NotificationService {
static Future<void> initialize() async {
// Écouter quand l'utilisateur clique sur une notification
FirebaseMessaging.instance.onMessageOpenedApp.listen((message) {
final deepLink = message.data['deepLink'];
if (deepLink != null) {
// Naviguer via le deep link
navigatorKey.currentState?.pushNamed(deepLink);
}
});
}
}

// Clé globale pour accéder au Navigator depuis n'importe où
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

class MyApp extends StatelessWidget {
const MyApp({super.key});


Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
initialRoute: '/',
onGenerateRoute: (settings) {
// Logique de routing...
return MaterialPageRoute(builder: (_) => const HomeScreen());
},
);
}
}
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';

// Partager un produit via schéma custom
void shareProduct(int productId) {
final deepLink = 'myapp://product/$productId';
Share.share('Regarde ce produit: $deepLink');
}

// Partager via HTTPS (Universal Link)
void shareProductViaWeb(int productId) {
final deepLink = 'https://myapp.com/product/$productId';
Share.share('Regarde ce produit: $deepLink');
}

// Envoyer par SMS
void shareProductViaSms(int productId) async {
final deepLink = 'https://myapp.com/product/$productId';
final uri = Uri(
scheme: 'sms',
path: '',
queryParameters: {'body': 'Clique ici: $deepLink'},
);

if (await canLaunchUrl(uri)) {
await launchUrl(uri);
}
}

Validation et sécurité

class DeepLinkValidator {
static Future<bool> handleDeepLink(String deepLink) async {
try {
final uri = Uri.parse(deepLink);

// Valider le schéma
if (uri.scheme != 'myapp' && uri.scheme != 'https') {
throw FormatException('Schéma non supporté: ${uri.scheme}');
}

// Valider le host (pour HTTPS)
if (uri.scheme == 'https' && uri.host != 'myapp.com') {
throw FormatException('Host non autorisé: ${uri.host}');
}

// Valider que le host n'est pas vide
if (uri.host.isEmpty) {
throw FormatException('Host vide dans le deep link');
}

// Naviguer si valide
navigatorKey.currentState?.pushNamed(deepLink);
return true;
} catch (e) {
debugPrint('Erreur deep link: $e');
return false;
}
}
}

Android (via ADB)

# Tester un deep link custom
adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/123" com.example.myapp

# Tester un deep link HTTPS
adb shell am start -W -a android.intent.action.VIEW -d "https://myapp.com/product/123" com.example.myapp

iOS (via Terminal)

# Ouvrir l'app avec un deep link
xcrun simctl openurl booted "myapp://product/123"

# Ou via HTTPS
xcrun simctl openurl booted "https://myapp.com/product/123"

Dans l'app (pour debug)

class DeepLinkTestScreen extends StatelessWidget {
const DeepLinkTestScreen({super.key});

void _testDeepLink(BuildContext context, String deepLink) {
Navigator.pushNamed(context, deepLink);
}


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Test Deep Links')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _testDeepLink(context, 'myapp://product/123'),
child: const Text('Tester myapp://product/123'),
),
ElevatedButton(
onPressed: () => _testDeepLink(context, 'myapp://search?query=flutter'),
child: const Text('Tester recherche'),
),
],
),
),
);
}
}

Bonnes pratiques

  1. Valider toujours : Vérifier le schéma, host et paramètres avant de naviguer
  2. Schémas uniques : Utiliser un schéma unique pour éviter les conflits (ex: com.mycompany.myapp://)
  3. Fallback : Prévoir une page par défaut si l'URL est invalide
  4. Sécurité : Ne jamais faire confiance aux données reçues via deep link
  5. Universal Links : Préférer HTTPS (Universal Links) pour une meilleure expérience utilisateur
  6. Documentation : Documenter tous les deep links supportés par l'app
  7. Analytics : Tracker les deep links pour mesurer leur utilisation
  8. Tester régulièrement : Vérifier que tous les deep links fonctionnent après chaque build

Ressources